<?php

namespace app\common\service;

use app\common\model\MemberOrder;
use app\common\model\OrderCommon;
use app\common\model\Plugin;
use EasyWeChat\Factory;
use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
use EasyWeChat\Kernel\Exceptions\InvalidConfigException;
use EasyWeChat\Kernel\Support\Collection;
use EasyWeChat\MiniProgram\Application;
use EasyWeChat\Payment\Jssdk\Client;
use GuzzleHttp\Exception\GuzzleException;
use Psr\Http\Message\ResponseInterface;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
use think\Model;

class WechatService extends PlatformBaseService
{
    public $platform = PlatFormFactory::WECHAT;

    /**
     * @var array|mixed|string
     */
    protected $payAppid;
    /**
     * @var mixed|string
     */
    protected $notifyUrl;

    /**
     * @param null $appid
     * @param null $notifyUrl
     */
    public function __construct(
        $appid = null,
        $notifyUrl = null
    ) {
        parent::__construct();

        $this->payAppid = $appid ?: $this->config['appid'];
        $this->notifyUrl = $notifyUrl ?: url('PayNotify/miniApp')->suffix(false)->domain(true)->build();
    }

    /**
     * @return Application
     */
    private function miniApp(): Application
    {
        $config = [
            'app_id' => $this->config['appid'],
            'secret' => $this->config['secret'],
            'log'    => [
                'level' => 'debug',
                'file'  => __DIR__ . '/wechat.log',
            ],
        ];
        return Factory::miniProgram($config);
    }

    /**
     * @return \EasyWeChat\Payment\Application
     */
    public function payApp(): \EasyWeChat\Payment\Application
    {
        $config = [
            // 必要配置
            'app_id'    => $this->payAppid,
            'mch_id'    => $this->config['mchid'],
            'key'       => $this->config['key'], // API v2 密钥 (注意: 是v2密钥 是v2密钥 是v2密钥)
            //            'sandbox' => env('wechat.sandbox', false), // 设置为 false 或注释则关闭沙箱模式

            // 如需使用敏感接口（如退款、发送红包等）需要配置 API 证书路径(登录商户平台下载 API 证书)
//            'cert_path' => get_sys_config('apiclient_cert'), // XXX: 绝对路径！！！！
//            'key_path'  => get_sys_config('apiclient_key'),      // XXX: 绝对路径！！！！

            'notify_url' => $this->notifyUrl,
        ];


        return Factory::payment($config);
    }

    /**
     * @param string $code
     * @return array
     * @throws InvalidConfigException
     */
    public function parseCode(string $code): array
    {
        $response = $this->miniApp()->auth->session($code);

        if (isset($response['errcode'])) {
            abort(400, $response['errmsg']);
        }

        return $response;
    }

    /**
     * @param string $page
     * @param $scene
     * @return string
     */
    public function qrcode(string $page, $scene): string
    {
        $response = $this->miniApp()->app_code->getUnlimit($scene, [
            'page'       => $page,
            'width'      => 600,
            'check_path' => false,
            //            'env_version' => 'develop'
        ]);

        if (is_array($response) && isset($response['errcode'])) {
            abort(400, $response['errmsg']);
        }

        return $response->getBody()->getContents();
    }

    /**
     * @param OrderCommon $model
     * @param string $tradeType
     * @return array|string
     * @throws InvalidArgumentException
     * @throws InvalidConfigException
     * @throws GuzzleException
     */
    public function createOrder(OrderCommon $model, string $tradeType = 'JSAPI')
    {
        $totalFee = $model->amount_payable;
        $body = $model->getPaymentBody();


        $app = $this->payApp();
        $params = [
            'body'         => $body,
            'out_trade_no' => $model->order_sn,
            'total_fee'    => $totalFee,
            'trade_type'   => $tradeType, // 请对应换成你的支付方式对应的值类型
        ];

        if ($tradeType === 'JSAPI') {
            $params['openid'] = $model->user->thirdWechat->openid;
        }

        $result = $app->order->unify($params);

        if (isset($result['return_code']) && $result['return_code'] !== 'SUCCESS') {
            abort(400, $result['return_msg']);
        }

        if (isset($result['result_code']) && $result['result_code'] === 'FAIL') {
            abort(400, $result['err_code_des']);
        }

        if ($tradeType === 'MWEB') {
            return $result;
        }

        return (new Client($app))->bridgeConfig($result['prepay_id'], false);
    }

    /**
     * @param OrderCommon $model
     * @return void
     * @throws InvalidConfigException
     */
    public function refundOrder(OrderCommon $model)
    {
        $result = $this->payApp()->refund->byOutTradeNumber($model->sn, $model->out_refund_no, $model->getData('paid_amount'), $model->getData('paid_amount'), [
            // 可在此处传入其他参数，详细参数见微信支付文档
            'refund_desc' => $model->refundDesc(),
        ]);

        if ($result['return_code'] !== 'SUCCESS') {
            abort(400, $result['return_msg']);
        }
    }

    /**
     * @param string $sn
     * @return array|Collection|object|ResponseInterface|string
     * @throws InvalidArgumentException
     * @throws InvalidConfigException
     * @throws DbException
     * @throws ModelNotFoundException
     */
    public function orderQuery(string $sn)
    {
        return $this->payApp()->order->queryByOutTradeNumber($sn);
    }

    /**
     * @param string $content
     * @return void
     * @throws InvalidConfigException
     * @throws GuzzleException
     * @throws DataNotFoundException
     * @throws DbException
     * @throws ModelNotFoundException
     */
    public function checkContent(string $content): void
    {
        $result = $this->miniApp()->content_security->checkText($content);

        if ($result['errcode'] > 0) {
            abort(400, '内容可能潜在风险,请修改后提交');
        }
    }

    /**
     * @param string $file
     * @return void
     * @throws InvalidConfigException
     * @throws GuzzleException
     * @throws DataNotFoundException
     * @throws DbException
     * @throws ModelNotFoundException
     */
    public function checkImage(string $file): void
    {
        $result = $this->miniApp()->content_security->checkImage($file);

        if ($result['errcode'] > 0) {
            abort(400, '图片违规，请重新上传');
        }
    }

    /**
     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
     * @throws \GuzzleHttp\Exception\GuzzleException
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     */
    public function toBalance(Model $model)
    {
        $withdrawalCommission = get_sys_config('withdrawal_commission') ?: 0;

        $result = $this->payApp()->transfer->toBalance([
            'partner_trade_no' => $model->sn, // 商户订单号，需保持唯一性(只能是字母或者数字，不能包含有符号)
            'openid'           => $model->user->thirdWechat->openid,
            'check_name'       => 'NO_CHECK', // NO_CHECK：不校验真实姓名, FORCE_CHECK：强校验真实姓名
            're_user_name'     => $model->user->nickname, // 如果 check_name 设置为FORCE_CHECK，则必填用户真实姓名
            'amount'           => $model->getData('money') * (1 - $withdrawalCommission / 100), // 企业付款金额，单位为分
            'desc'             => '提现', // 企业付款操作说明信息。必填
        ]);

        if (isset($result['err_code'])) {
            abort(400, $result['err_code'] . ':' . $result['err_code_des']);
        }

        return $result;
    }

    /**
     * @return void
     * @throws \EasyWeChat\Kernel\Exceptions\HttpException
     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
     * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
     * @throws \GuzzleHttp\Exception\GuzzleException
     * @throws \Psr\SimpleCache\InvalidArgumentException
     */
    public function getPhoneNumber(): string
    {
        validate([
            'code' => 'require'
        ])->check(request()->param());

        $accessToken = $this->miniApp()->access_token->getToken();

        $client = new \GuzzleHttp\Client([
            'base_uri' => 'https://api.weixin.qq.com',
        ]);

        $code = request()->param('code');

        $resp = $client->request('POST', '/wxa/business/getuserphonenumber', [
            'query' => [
                'access_token' => $accessToken['access_token'],
            ],
            'json'  => [
                'code' => $code,
            ]
        ]);

        $content = json_decode($resp->getBody()->getContents(), true);

        if ($content['errcode'] !== 0) {
            if ($content['errcode'] === 40001) {
                $this->miniApp()->access_token->refresh();
                abort(400, '服务繁忙，请重试');
            }

            abort(400, $content['errcode'] . ':' . $content['errmsg']);
        }

        return $content['phone_info']['purePhoneNumber'];
    }
}